package token

import (
	"errors"
	"fmt"
	"time"

	"gitee.com/flwwsg/apiserver/pkg/errno"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
)

var (
	ErrMissingHeader = errors.New("the length of the `Authorization` header is zero")
)

type Context struct {
	ID       uint64
	Username string
}

//检查 secret 格式
func secretFunc(secret string) jwt.Keyfunc {
	return func(token *jwt.Token) (i interface{}, e error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, jwt.ErrSignatureInvalid
		}
		return []byte(secret), nil
	}
}

//校验 token
func Parse(tokenString string, secret string) (*Context, error) {
	ctx := &Context{}
	token, err := jwt.Parse(tokenString, secretFunc(secret))
	if err != nil {
		//parse error
		return ctx, err
	} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		expired := int64(claims["exp"].(float64))
		if time.Now().Unix() > expired {
			return ctx, errno.ErrTokenExpired
		}
		ctx.ID = uint64(claims["id"].(float64))
		ctx.Username = claims["username"].(string)
		return ctx, nil
	} else {
		//other errors
		return ctx, err
	}
}

//gets the token from the header and pass it to the parse function
func ParseRequest(c *gin.Context) (*Context, error) {
	header := c.Request.Header.Get("Authorization")
	secret := viper.GetString("jwt_secret")
	if len(header) == 0 {
		return &Context{}, ErrMissingHeader
	}
	//从头部获取 token
	var t string
	_, err := fmt.Sscanf(header, "Bearer %s", &t)
	if err != nil {
		panic(err)
	}
	return Parse(t, secret)
}

func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {
	if secret == "" {
		secret = viper.GetString("jwt_secret")
	}
	now := time.Now().UTC()
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       c.ID,
		"username": c.Username,
		//not before：token在此时间之前不能被接收处理
		"nbf": now,
		//exp(expires): token什么时候过期
		//issued at: 在什么时候签发的token
		"iat": now,
		"exp": now.Add(time.Duration(viper.GetInt("jwt_expire")) * time.Second).Unix(),
	})
	tokenString, err = token.SignedString([]byte(secret))
	return
}
